// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This file is part of CodingEditor.
// Note:	This project is derived from Peter Project
//			(hosted on sourceforge and codeplex)
//
// Copyright (c) 2008-2009, CE Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


namespace CE.Main
{
    using System;
    using System.Collections;
    using System.Diagnostics;
    using System.Drawing;
    using System.IO;
    using System.Runtime.Remoting.Messaging;
    using System.Text.RegularExpressions;
    using System.Windows.Forms;
    using System.Xml;
    using CE.API;
    using CE.Classes;
    using CE.Parsers.CS;
    using WeifenLuo.WinFormsUI.Docking;
    using Parser = CE.Parsers.C.Parser;
    using Scanner = CE.Parsers.C.Scanner;

	#region Delegates

	public delegate void StartParsing(ArrayList fileList, cProjectInfo projNode);

	#endregion Delegates

	public sealed partial class ProjectExplorer : DockContent, ICEPluginTab
	{
		#region Fields

		private readonly MainForm m_MainForm;
		private readonly TraceDelegate m_delTrace;

		private ICEPluginHost m_Host;
		private ContextMenuStrip m_ctxRoot;

		#endregion Fields

		#region Constructors

		public ProjectExplorer(MainForm main)
		{
			InitializeComponent();

			m_MainForm = main;
			m_ctxRoot = new ContextMenuStrip();
			var tsmiBuild = new ToolStripMenuItem("Build");
			tsmiBuild.Click += Build;
			var tsmiRmvProject = new ToolStripMenuItem("Remove Project");
			tsmiRmvProject.Click += RemoveProject;

			m_ctxRoot.Items.Add(tsmiBuild);
			m_ctxRoot.Items.Add(tsmiRmvProject);

			TabText = "Projects";
			m_delTrace = m_MainForm.ChangeStatus;
			treeMain.BeforeExpand += treeMain_BeforeExpand;
			treeMain.DoubleClick += treeMain_DoubleClick;
			treeMain.AfterSelect += treeMain_AfterSelect;
			ToolStripManager.Renderer = new CE.CustomControls.Office2007Renderer();
		}

		#endregion Constructors

		#region Properties

		public bool AbleToCopy
		{
			get { return true; }
		}

		public bool AbleToCut
		{
			get { return false; }
		}

		public bool AbleToDelete
		{
			get { return true; }
		}

		public bool AbleToPaste
		{
			get { return false; }
		}

		public bool AbleToRedo
		{
			get { return false; }
		}

		public bool AbleToSave
		{
			get { return true; }
		}

		public bool AbleToSelectAll
		{
			get { return false; }
		}

		public bool AbleToUndo
		{
			get { return false; }
		}

		public string FileName
		{
			get { return string.Empty; }
		}

		public ICEPluginHost Host
		{
			get { return m_Host; }

			set { m_Host = value; }
		}

		public bool NeedsSaving
		{
			get { return false; }
		}

		/// <summary>
		/// Gets or Sets the Context Menu used for the Root nodes...
		/// </summary>
		public ContextMenuStrip RootContextMenu
		{
			get { return m_ctxRoot; }

			set { m_ctxRoot = value; }
		}

		public string Selection
		{
			get{
				if (treeMain.SelectedNode != null) {
					return treeMain.SelectedNode.Tag.ToString();
				}

				return string.Empty;
			}
		}

		#endregion Properties

		#region Methods

		public void CallBack(IAsyncResult ar)
		{
			var del = (StartParsing) ((AsyncResult) ar).AsyncDelegate;
			Console.WriteLine("--- Done Parsing ---");
			del.EndInvoke(ar);
		}

		public string CheckFileInProject(string filePath)
		{
			try {
				foreach (TreeNode node in treeMain.Nodes) {
					var pInfo = (cProjectInfo) node.Tag;
					if (pInfo.Data != null) {
						foreach (TreeNode n in pInfo.Data.Nodes["files"].Nodes) {
							if (n.Text.ToLower().Equals(n.Text.ToLower())) {
								return pInfo.Path;
							}
						}
					}
				}
			}
			catch (Exception ex) {
				Console.WriteLine(ex.Message);
			}

			return null;
		}

		public bool CloseTab()
		{
			Close();
			return true;
		}

		public void Copy()
		{
		}

		public void Cut()
		{
		}

		public void Delete()
		{
			if (treeMain.SelectedNode.Parent == null) {
				// Delete Project...
				if (MessageBox.Show(
				            "This will permanently delete the project '" + treeMain.SelectedNode.Text + "'.\nAre you OK with that?",
				            "CodingEditor", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes) {
					if (File.Exists(treeMain.SelectedNode.Tag.ToString())) {
						File.Delete(treeMain.SelectedNode.Tag.ToString());
						return;
					}
				}
				return;
			}

			TreeNode root = treeMain.SelectedNode;
			while (root.Parent != null) {
				root = root.Parent;
			}
			var xDoc = new XmlDocument();
			xDoc.Load(root.Tag.ToString());
			foreach (XmlNode node in xDoc.FirstChild.ChildNodes) {
				if (node.InnerText == treeMain.SelectedNode.Tag.ToString()) {
					xDoc.FirstChild.RemoveChild(node);

					treeMain.SelectedNode.Parent.Nodes.Remove(treeMain.SelectedNode);
					return;
				}
			}

			MessageBox.Show("Unable to remove the specified file/folder.", "CodingEditor",
			                MessageBoxButtons.OK, MessageBoxIcon.Information);
		}

		public void Duplicate()
		{
		}

		public bool FindNext( Regex reg, bool searchUp)
		{
			return false;
		}

		/// <summary>
		/// Loads the given project...
		/// </summary>
		/// <param name="filePath"></param>
		/// <returns></returns>
		public string LoadFile(string filePath)
		{
			// Check to see if the project is open...
			foreach (TreeNode rN in treeMain.Nodes) {
				if (rN.Tag.ToString() == filePath) {
					// The project is already open, leave...
					return null;
				}
			}

			// Does the file Exist???
			if (File.Exists(filePath)) {
				var pInfo = new cProjectInfo();

				// Load project file...
				var xDoc = new XmlDocument();
				xDoc.Load(filePath);

				// Get Project Name...
				XmlNodeList nodes = xDoc.GetElementsByTagName("name");
				if (nodes.Count < 1) {
					// No name, get out of here...
					return null;
				}
				string proj = nodes[0].InnerText;

				// Set up Root Node...
				var root = new TreeNode(proj) {
					Name = proj
				};
				root.ImageIndex = root.SelectedImageIndex = 1;
				root.ContextMenuStrip = m_ctxRoot;

				// Get Project Type...
				nodes = xDoc.GetElementsByTagName("type");
				if (nodes.Count > 0) {
					pInfo.Type = nodes[0].InnerText;
					if (string.IsNullOrEmpty(pInfo.Type)) pInfo.Type = "None";
				} else {
					pInfo.Type = "None";
				}

				// Set up Project Info...
				pInfo.Name = proj;
				pInfo.Path = filePath;
				root.Tag = pInfo;

				// List of folders and files...
				var folders = new ArrayList();
				var files = new ArrayList();

				// Get Folders...
				nodes = xDoc.GetElementsByTagName("folder");
				foreach (XmlNode node in nodes) {
					GetFolder(root, node);
					folders.Add(node.InnerText);
				}

				// Get Files...
				nodes = xDoc.GetElementsByTagName("file");
				foreach (XmlNode node in nodes) {
					GetFile(root, node);
					files.Add(node.InnerText);
				}

				// Add Project to TreeView...
				treeMain.Nodes.Add(root);
				treeMain.SelectedNode = root;

				// Parse Project...
				if (!pInfo.Equals("None")) {
					var fileList = new ArrayList();
					var filters = new string[1];
					filters[0] = "*.*";
					switch (pInfo.Type.ToLower()) {
					    case "c project":
						    filters = new string[2];
						    filters[0] = "*.c";
						    filters[1] = "*.h";
						    break;
					}

					foreach (string folder in folders) {
						var di = new DirectoryInfo(folder);
						foreach (string filter in filters) {
							FileInfo[] dirFiles = di.GetFiles(filter, SearchOption.AllDirectories);
							foreach (FileInfo fi in dirFiles) {
								fileList.Add(fi);
							}
						}
					}
					foreach (string file in files) {
						switch (pInfo.Type.ToLower()) {
						    case "c project":
							    foreach (string filter in filters) {
								    if (filter.Equals("*" + Path.GetExtension(file))) {
									    fileList.Add(new FileInfo(file));
								    }
							    }
							    break;
						}
					}

					StartParsing del = ParseProject;
					del.BeginInvoke(fileList, pInfo, CallBack, null);
				}

				return proj;
			}
			return null;
		}

		public string LookUp(string word, string project)
		{
			string rtn = "<ul>";
			TreeNode pData = GetProjectData(project);
			if (pData != null) {
				foreach (TreeNode fileNode in pData.Nodes) {
					foreach (TreeNode dataNode in fileNode.Nodes) {
						foreach (TreeNode infoNode in dataNode.Nodes) {
							if (infoNode.Text.Contains(word)) {
								rtn += "<li><b><a h=\"" + fileNode.Text + "\" TITLE=\"" + fileNode.Text + "\" offset=\"" +
								       infoNode.Tag + "\">" + Path.GetFileName(fileNode.Text);
								rtn += "</a></b><ul><li>" + infoNode.Text + "</li></ul></li>";
							}
						}
					}
				}
			}
			rtn += "</ul>";
			return rtn;
		}

		public void MarkAll( Regex reg)
		{
		}

		public void ParseProject(ArrayList fileList, cProjectInfo pInfo)
		{
			//cProjectInfo pInfo = (cProjectInfo)projNode.Tag;
			pInfo.Data = new TreeNode("root");

			switch (pInfo.Type.ToLower()) {
			    case "c project":
				    foreach (FileInfo fi in fileList) {
					    ParseCFile(pInfo.Data, fi);
				    }
				    break;
			}
			m_delTrace.BeginInvoke(pInfo.Name + " Project Data Complete.", null, null);
		}

		public void Paste()
		{
		}

		public void Print(bool showDialog)
		{
		}

		public void Redo()
		{
		}

		public void ReplaceAll( Regex reg,  string replaceWith)
		{
		}

		public void ReplaceNext( Regex reg,  string replaceWith, bool searchUp)
		{
		}

		public void Save()
		{
		}

		public void SaveAs( string filePath)
		{
		}

		public void SelectAll()
		{
		}

		public void SelectWord(int line, int offset, int wordLeng)
		{
		}

		public void Undo()
		{
		}

		protected override string GetPersistString()
		{
			string projs = string.Empty;
			foreach (TreeNode node in treeMain.Nodes) {
				var pInfo = (cProjectInfo) node.Tag;
				projs += pInfo.Path + ";";
			}

			return GetType() + "|" + projs.TrimEnd(new[] {';'});
		}

		private static void GetFolder(TreeNode root, XmlNode node)
		{
			var tNode = new TreeNode();
			string folder = node.InnerText;
			tNode.Tag = folder;
			folder = folder.Substring(folder.LastIndexOf('\\') + 1);
			tNode.Text = tNode.Name = folder;
			tNode.Nodes.Add(string.Empty);

			root.Nodes.Add(tNode);
		}

		private void Build(object sender, EventArgs e)
		{
			string script = string.Empty;
			string workingDir = string.Empty;
			string proj = treeMain.SelectedNode.Tag.ToString();

			var xDoc = new XmlDocument();
			xDoc.Load(proj);

			// Working Dir...
			XmlNodeList nodes = xDoc.GetElementsByTagName("WorkingDir");
			if (nodes.Count == 1) {
				workingDir = nodes[0].InnerText;
			}
			if (string.IsNullOrEmpty(workingDir)) {
				workingDir = Application.StartupPath;
			}

			// Build Script...
			nodes = xDoc.GetElementsByTagName("BuildScript");
			if (nodes.Count == 1) {
				script = nodes[0].InnerText;
			}

			// Pre-Build/Post-Build Files...
			string preBuild = string.Empty, postBuild = Environment.NewLine;
			nodes = xDoc.GetElementsByTagName("BuildFile");
			foreach (XmlNode node in nodes) {
				bool bpreBuild = false;
				string file = string.Empty;
				foreach (XmlNode cNode in node.ChildNodes) {
					if (cNode.Name.ToLower() == "file") {
						file = cNode.InnerText;
					}

					if (cNode.Name.ToLower() == "prebuild") {
						bpreBuild = Convert.ToBoolean(cNode.InnerText);
					}
				}

				if (bpreBuild) {
					preBuild += file + Environment.NewLine;
				} else {
					postBuild += file + Environment.NewLine;
				}
			}

			script = preBuild + script + postBuild;
			m_MainForm.RunScript(script, workingDir);
		}

		private void GetDirContent(TreeNode node)
		{
			node.Nodes.Clear();
			var dirInfo = new DirectoryInfo(node.Tag.ToString());
			if (dirInfo.Exists) {
				DirectoryInfo[] dirs = dirInfo.GetDirectories();
				Array.Sort(dirs, new DirectorySorter());
				foreach (DirectoryInfo dir in dirs) {
					var tNode = new TreeNode {Tag = dir.FullName};
					string folder = dir.FullName.Substring(dir.FullName.LastIndexOf('\\') + 1);
					tNode.Text = tNode.Name = folder;
					tNode.Nodes.Add(string.Empty);

					node.Nodes.Add(tNode);
				}

				FileInfo[] files = dirInfo.GetFiles();
				Array.Sort(files, new FileSorter());
				foreach (FileInfo file in files) {
					var tNode = new TreeNode {Tag = file.FullName};
					tNode.Text = tNode.Name = Path.GetFileName(file.FullName);
					GetNodeImage(tNode);

					node.Nodes.Add(tNode);
				}
			} else {
				MessageBox.Show("Directory Not Found.", "CodingEditor", MessageBoxButtons.OK, MessageBoxIcon.Error);
			}
		}

		private void GetFile(TreeNode root, XmlNode node)
		{
			var tNode = new TreeNode();
			string file = node.InnerText;
			tNode.Tag = file;
			file = Path.GetFileName(file);
			tNode.Text = tNode.Name = file;
			GetNodeImage(tNode);

			root.Nodes.Add(tNode);
		}

		private void GetNodeImage(TreeNode node)
		{
			Image image = Common.GetFileIcon(node.Tag.ToString(), false).ToBitmap();
			int index = imgMain.Images.Add(image, Color.Transparent);

			node.SelectedImageIndex = node.ImageIndex = index;
		}

		private TreeNode GetProjectData(string project)
		{
			foreach (TreeNode node in treeMain.Nodes) {
				var pInfo = (cProjectInfo) node.Tag;
				if (pInfo.Path.Equals(project)) {
					return pInfo.Data;
				}
			}

			return null;
		}

		private void ParseCFile(TreeNode data, FileSystemInfo fi)
		{
			m_delTrace.BeginInvoke("Gathering Project Data for file: " + fi.FullName, null, null);
			var file = new TreeNode(fi.FullName) {
				Name = fi.FullName
			};

			var prototypes = new TreeNode("prototypes") {
				Name = "prototypes"
			};

			var variables = new TreeNode("variables") {
				Name = "variables"
			};

			var defines = new TreeNode("defines") {
				Name = "defines"
			};

			var scanner = new Scanner(fi.FullName);
			var parser = new Parser(scanner);
			parser.Parse();
			foreach (TokenMatch tm in parser.CodeInfo.Defines) {
				var node = new TreeNode(tm.Value) {
					Name = tm.Value, Tag = tm.Position
				};
				defines.Nodes.Add(node);
			}
			foreach (TokenMatch tm in parser.CodeInfo.Prototypes) {
				var node = new TreeNode(tm.Value) {
					Name = tm.Value, Tag = tm.Position
				};
				prototypes.Nodes.Add(node);
			}
			foreach (TokenMatch tm in parser.CodeInfo.Functions) {
				var node = new TreeNode(tm.Value) {
					Name = tm.Value, Tag = tm.Position
				};
				prototypes.Nodes.Add(node);
			}
			foreach (TokenMatch tm in parser.CodeInfo.GlobalVariables) {
				var node = new TreeNode(tm.Value) {
					Name = tm.Value, Tag = tm.Position
				};
				variables.Nodes.Add(node);
			}

			if (defines.Nodes.Count > 0) file.Nodes.Add(defines);
			if (prototypes.Nodes.Count > 0) file.Nodes.Add(prototypes);
			if (variables.Nodes.Count > 0) file.Nodes.Add(variables);

			if (file.Nodes.Count > 0) data.Nodes.Add(file);
		}

		private void ProjectManager_FormClosed(object sender,
		                                       FormClosedEventArgs e)
		{
			Dispose(true);
		}

		private void RemoveProject(object sender, EventArgs e)
		{
			try {
				var pInfo = (cProjectInfo) treeMain.SelectedNode.Tag;
				if (pInfo.Data != null) {
					pInfo.Data.Nodes.Clear();
				}
				GC.Collect();
				treeMain.SelectedNode.Tag = null;
				treeMain.Nodes.Remove(treeMain.SelectedNode);
			}
			catch (Exception ex) {
				Console.WriteLine(ex.Message);
			}
		}

		private void treeMain_AfterSelect(object sender, TreeViewEventArgs e)
		{
			if (treeMain.SelectedNode != null) {
				tsbDelete.Enabled = true;
				if (treeMain.SelectedNode.Parent == null) {
					tsbAddFile.Enabled = tsbAddFolder.Enabled =
					                             tsbProperties.Enabled = tsbRefresh.Enabled = true;
					tsbOpenDir.Enabled = false;
				} else if (treeMain.SelectedNode.Nodes.Count == 0) {
					tsbProperties.Enabled = tsbAddFolder.Enabled = tsbRefresh.Enabled =
					                                                       tsbAddFile.Enabled = false;
					tsbOpenDir.Enabled = true;
				} else {
					tsbRefresh.Enabled = tsbOpenDir.Enabled = true;
					tsbProperties.Enabled = tsbAddFolder.Enabled = tsbAddFile.Enabled = false;
				}
			} else {
				tsbAddFile.Enabled = tsbAddFolder.Enabled = tsbDelete.Enabled =
				                                                    tsbProperties.Enabled = tsbRefresh.Enabled = tsbOpenDir.Enabled = false;
			}
		}

		private void treeMain_BeforeExpand(object sender, TreeViewCancelEventArgs e)
		{
			if (e.Node.Tag.ToString().ToLower() != "vfolder") {
				if (!treeMain.Nodes.Contains(e.Node)) {
					GetDirContent(e.Node);
				}
			}
		}

		private void treeMain_DoubleClick(object sender, EventArgs e)
		{
            OpenSelectedFileInEditor();
		}

        private void OpenSelectedFileInEditor()
        {
            if (treeMain.SelectedNode != null) {
                if (treeMain.SelectedNode.Nodes.Count == 0) {
                    TreeNode n = treeMain.SelectedNode.Parent;
                    while (n.Parent != null) {
                        n = n.Parent;
                    }
                    var pInfo = (cProjectInfo)n.Tag;
                    string file = treeMain.SelectedNode.Tag.ToString();
                    string fileName = Path.GetFileName(file);
                    m_Host.CreateEditor(file, fileName);
                    m_MainForm.GetEditor(file).Project = pInfo.Path;
                }
            }
        }

		private void tsbAddFile_Click(object sender, EventArgs e)
		{
            AddExistingFile();
		}

        private void AddExistingFile()
        {
            if (ofdMain.ShowDialog() == DialogResult.OK) {
                string proj = treeMain.SelectedNode.Tag.ToString();
                var xDoc = new XmlDocument();
                xDoc.Load(proj);

                foreach (string file in ofdMain.FileNames) {
                    foreach (TreeNode node in treeMain.SelectedNode.Nodes) {
                        if (node.Tag.ToString() == file) {
                            return;
                        }
                    }
                    XmlNode addNode = xDoc.CreateNode(XmlNodeType.Element, "file", null);
                    addNode.InnerText = file;
                    xDoc.FirstChild.AppendChild(addNode);

                    var newFile = new TreeNode(Path.GetFileName(file));
                    newFile.Name = newFile.Text;
                    newFile.Tag = file;
                    GetNodeImage(newFile);
                    treeMain.SelectedNode.Nodes.Add(newFile);
                }
                xDoc.Save(proj);
            }
        }

		private void tsbAddFolder_Click(object sender, EventArgs e)
		{
            AddExistingFolder();
		}

        private void AddExistingFolder()
        {
            if (fbdMain.ShowDialog() == DialogResult.OK) {
                string path = fbdMain.SelectedPath;

                if (treeMain.SelectedNode == null) return;

                string proj = treeMain.SelectedNode.Tag.ToString();

                foreach (TreeNode node in treeMain.SelectedNode.Nodes) {
                    if (node.Tag.ToString() == path) {
                        return;
                    }
                }

                var xDoc = new XmlDocument();
                xDoc.Load(proj);
                XmlNode addNode = xDoc.CreateNode(XmlNodeType.Element, "folder", null);
                addNode.InnerText = path;
                xDoc.FirstChild.AppendChild(addNode);
                xDoc.Save(proj);

                var newFolder = new TreeNode(path.Substring(path.LastIndexOf('\\') + 1));
                newFolder.Name = newFolder.Text;
                newFolder.Tag = path;
                newFolder.Nodes.Add(string.Empty);
                treeMain.SelectedNode.Nodes.Add(newFolder);
            }
        }

		private void tsbDelete_Click(object sender, EventArgs e)
		{
			Delete();
		}

		private void tsbOpenDir_Click(object sender, EventArgs e)
		{
            OpenContainingFolder();
		}

        private void OpenContainingFolder()
        {
            if (treeMain.SelectedNode != null && treeMain.SelectedNode.Tag != null) {
                if (treeMain.SelectedNode.Nodes.Count == 0) {
                    Process.Start("explorer.exe",
                                  Path.GetDirectoryName(treeMain.SelectedNode.Tag.ToString()));
                } else {
                    Process.Start("explorer.exe", treeMain.SelectedNode.Tag.ToString());
                }
            }
        }

		private void tsbProperties_Click(object sender, EventArgs e)
		{
            ShowProjectProperties();
		}

        private void ShowProjectProperties()
        {
            var prj = new CEProject();
            prj.SetProjectFile(treeMain.SelectedNode.Tag.ToString());
            prj.ShowDialog();
        }

		private void tsbRefresh_Click(object sender, EventArgs e)
		{
            RefreshTreeView();
		}

        private void RefreshTreeView()
        {
            if (treeMain.SelectedNode != null) {
                if (treeMain.SelectedNode.Parent == null) {
                    // Refresh the whole project...
                    foreach (TreeNode node in treeMain.SelectedNode.Nodes) {
                        node.Collapse();
                        node.Expand();
                    }
                } else {
                    // Refresh Folder...
                    treeMain.SelectedNode.Collapse();
                    treeMain.SelectedNode.Expand();
                }
            }
        }

		#endregion Methods
	}
}